/**
 * WTF Engine
 *
 * License... etc.
 **
 * Render Module
 *
 * Provides:
 *	- Modelview matrix transforms
 *	- render routine
 *  - bindings to important OpenGL calls
 **
 * Authors: Sean Micklethwaite
 **/


#include "render.h"
#include "glext.h"
#include "graphics_functions.h"
#include "glsl.h"

#if !defined(NDEBUG) && defined(__PHYSICS_BULLET__)
//#	define _RENDER_PHYSICS_DEBUG
//#	define _RENDER_NO_OBJECTS
#	include "physics.h"
#endif

using namespace WtfEngine;

PFNGLBINDRENDERBUFFEREXTPROC		glBindRenderbufferEXT;
PFNGLRENDERBUFFERSTORAGEEXTPROC		glRenderbufferStorageEXT;
PFNGLBINDFRAMEBUFFEREXTPROC			glBindFramebufferEXT;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC	glCheckFramebufferStatusEXT;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC	glFramebufferTexture2DEXT;
PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT;
PFNGLGENERATEMIPMAPEXTPROC			glGenerateMipmapEXT;
PFNGLGENRENDERBUFFERSEXTPROC		glGenRenderbuffersEXT;
PFNGLGENFRAMEBUFFERSEXTPROC			glGenFramebuffersEXT;
PFNGLDELETERENDERBUFFERSEXTPROC		glDeleteRenderbuffersEXT;
PFNGLDELETEFRAMEBUFFERSEXTPROC		glDeleteFramebuffersEXT;
PFNGLDRAWBUFFERSPROC				glDrawBuffers;

// VBO Related
PFNGLGENBUFFERSARBPROC				glGenBuffersARB;
PFNGLBINDBUFFERARBPROC				glBindBufferARB;
PFNGLBUFFERDATAARBPROC				glBufferDataARB;
PFNGLDELETEBUFFERSARBPROC			glDeleteBuffersARB;

// GLSL Related
PFNGLCREATESHADEROBJECTARBPROC		glCreateShaderObjectARB;
PFNGLSHADERSOURCEARBPROC			glShaderSourceARB;
PFNGLCOMPILESHADERARBPROC			glCompileShaderARB;
PFNGLGETOBJECTPARAMETERIVARBPROC	glGetObjectParameterivARB;
PFNGLGETINFOLOGARBPROC				glGetInfoLogARB;
PFNGLCREATEPROGRAMOBJECTARBPROC		glCreateProgramObjectARB;
PFNGLATTACHOBJECTARBPROC			glAttachObjectARB;
PFNGLLINKPROGRAMARBPROC				glLinkProgramARB;
PFNGLUSEPROGRAMOBJECTARBPROC		glUseProgramObjectARB;
PFNGLGETUNIFORMLOCATIONARBPROC		glGetUniformLocationARB;
PFNGLGETATTRIBLOCATIONARBPROC		glGetAttribLocationARB;
PFNGLUNIFORM2FARBPROC				glUniform2fARB;
PFNGLUNIFORM3FARBPROC				glUniform3fARB;
PFNGLUNIFORM1IARBPROC				glUniform1iARB;
PFNGLVERTEXATTRIBPOINTERARBPROC		glVertexAttribPointerARB;
PFNGLENABLEVERTEXATTRIBARRAYARBPROC	glEnableVertexAttribArrayARB;
PFNGLDISABLEVERTEXATTRIBARRAYARBPROC	glDisableVertexAttribArrayARB;

#ifndef __GNUC__
PFNGLACTIVETEXTUREARBPROC			glActiveTextureARB;
PFNGLCLIENTACTIVETEXTUREPROC		glClientActiveTexture;
#endif

Renderable::Renderable(const Renderable::tCreationOptions& args, const WtfGraphics::GraphicsData::tCacheRef& d)
	: mrGraphicsData(d), GameObject(args)
{
	Renderer::GetInstance().PushObject(this);
};

void Renderable::Destroy()
{
	Renderer::GetInstance().RemoveObject(this);
	this->GameObject::Destroy();
};


unsigned Renderable::CalculateSerializedSize() {
	// Need to serialize the graphics data reference.
	return GameObject::CalculateSerializedSize() + mrGraphicsData.CalculateSerializedSize();
};
unsigned Renderable::Serialize(void *pBuf) {
	unsigned n = GameObject::Serialize(pBuf);
	return n + mrGraphicsData.Serialize((void *)((unsigned)pBuf + n));
};
unsigned Renderable::Deserialize(void *pBuf) {
	unsigned n = GameObject::Deserialize(pBuf);
	return n + mrGraphicsData.Deserialize((void *)((unsigned)pBuf + n));
};


/**
 * Deferred shader using only diffuse map
 **/
class PostProcess: public GLSLProgram {
	int mDiffuseTexLocation;

public:
	PostProcess( const String& sShader )
	{
		GLSLShader::tRef rVert, rFrag;

		rVert = new GLSLShader();
		rVert->Load(sShader + ".vert");
		
		rFrag = new GLSLShader();
		rFrag->Load(sShader + ".frag");

		this->Load(rVert, rFrag);
	};
	virtual ~PostProcess() {};

	virtual void Activate()
	{
		GLSLProgram::Activate();

		glUniform1iARB( mDiffuseTexLocation, 0 );
	};

	virtual void GetLocations()
	{
		mDiffuseTexLocation = GetUniformLocation("diffuseTex");
	};
};

class PostProcessSampler: public PostProcess {
	Texture::tRef mrSampler;

	int mSamplerLocation;

public:
	PostProcessSampler( const String& sShader, Texture::tRefa rSampler )
		: PostProcess(sShader), mrSampler(rSampler)
	{
		mSamplerLocation = GetUniformLocation("gSampler");
	}

	virtual void Activate()
	{
		mrSampler->Bind( 1 );
		PostProcess::Activate();
		glUniform1iARB( mSamplerLocation, 1 );
	};

	virtual void Deactivate()
	{
		PostProcess::Deactivate();
		mrSampler->Release( 1 );
	};
};

class DeferredShader: public PostProcess {
	FBOTexture::tRef		mrDeferredShaderData;
	Texture::tCacheRef		mrRandomNormals;

	int mNormalDepthTexLocation;
	int mSpecularPropertiesLocation;
	int mRandomNormalsTexLocation;

public:
	DeferredShader( const String& sShader, FBOTexture::tRefa rData )
		: PostProcess(sShader), mrDeferredShaderData(rData), mrRandomNormals("media/textures/rnm.png")
	{
		mNormalDepthTexLocation = GetUniformLocation("normalDepthTex");
		mRandomNormalsTexLocation = GetUniformLocation("rnmTex");
		mSpecularPropertiesLocation = GetUniformLocation("specularTex");
	}

	void Activate()
	{
		mrDeferredShaderData->BindExtra( 0, 1 );
		mrDeferredShaderData->BindExtra( 1, 3 );
		mrRandomNormals->Bind( 2 );

		PostProcess::Activate();

		glUniform1iARB( mNormalDepthTexLocation, 1 );
		glUniform1iARB( mRandomNormalsTexLocation, 2 );
		glUniform1iARB( mSpecularPropertiesLocation, 3);
	};

	void Deactivate()
	{
		PostProcess::Deactivate();

		mrRandomNormals->Release( 2 );
		mrDeferredShaderData->ReleaseExtra( 1, 3 );
		mrDeferredShaderData->ReleaseExtra( 0, 1 );
	};
};


/**** Render passes ****/

void Renderer::GenericPass::Activate()
{
	rTarget->Activate();
};

void Renderer::GenericPass::Deactivate()
{
	rTarget->Deactivate();
};

void Renderer::PostPass::Activate()
{
	Renderer::GenericPass::Activate();
	rSource->Bind( 0 );
	rShader->Activate();
};

void Renderer::PostPass::Deactivate()
{
	rShader->Deactivate();
	rSource->Release( 0 );
	Renderer::GenericPass::Deactivate();
};

void Renderer::PostPass::Render()
{
	Activate();
	
	glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
	
	WtfGraphics::SetupOrthoProjection(rTarget);
	WtfGraphics::Draw2DBox(iVector2D(0,0), rTarget->getSize());
	
	Deactivate();
};

class MainRenderPass: public Renderer::GenericPass {
public:
	MainRenderPass(): Renderer::GenericPass("d_mainRender") {
		rTarget = Renderer::GetInstance().getDeferredShaderData();
	};

	void Activate()
	{
		Renderer::GenericPass::Activate();
		Renderer::GetInstance().getDeferredShaderData()->ActivateExtra();
	}

	void Deactivate()
	{
		Renderer::GetInstance().getDeferredShaderData()->DeactivateExtra();
		Renderer::GenericPass::Deactivate();
	}
		
	void Render()
	{
		Activate();

		// Background Colour
		glClearColor (0.5f, 0.6f, 0.8f, 0.0f);
		glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

		Renderer::GetInstance().GetCamera()->EnterPerspective(rTarget);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glDepthMask(GL_TRUE);
		glDisable(GL_STENCIL_TEST);
		glDisable(GL_BLEND);

		// Render world
		Renderer::GetInstance().RenderAll();

		Renderer::GetInstance().GetCamera()->LeavePerspective();

		Deactivate();
	}
};


/**** The renderer ****/

Renderer::Renderer(WtfGraphics::tScreenRef& screen)
: mScreen(screen), Task<Renderer>(500, "Renderer"), Loggable<Renderer>("Renderer"), mRenderType(RENDER_WORLD)
{
	//
	// Load extension functions
	//

	glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC)
		SDL_GL_GetProcAddress("glBindRenderbufferEXT");
	glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC)
		SDL_GL_GetProcAddress("glRenderbufferStorageEXT");
	glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)
		SDL_GL_GetProcAddress("glBindFramebufferEXT");
	glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
		SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
	glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
		SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
	glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)
		SDL_GL_GetProcAddress("glFramebufferRenderbufferEXT");
	glGenerateMipmapEXT = (PFNGLGENERATEMIPMAPEXTPROC)
		SDL_GL_GetProcAddress("glGenerateMipmapEXT");
	glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC)
		SDL_GL_GetProcAddress("glGenRenderbuffersEXT");
	glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)
		SDL_GL_GetProcAddress("glGenFramebuffersEXT");
	glDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC)
		SDL_GL_GetProcAddress("glDeleteRenderbuffersEXT");
	glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
		SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
	glDrawBuffers = (PFNGLDRAWBUFFERSPROC)
		SDL_GL_GetProcAddress("glDrawBuffers");
	
	// VBO Related
	glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)
		SDL_GL_GetProcAddress("glGenBuffersARB");
	glBindBufferARB = (PFNGLBINDBUFFERARBPROC)
		SDL_GL_GetProcAddress("glBindBufferARB");
	glBufferDataARB = (PFNGLBUFFERDATAARBPROC)
		SDL_GL_GetProcAddress("glBufferDataARB");
	glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)
		SDL_GL_GetProcAddress("glDeleteBuffersARB");
	glVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)
		SDL_GL_GetProcAddress("glVertexAttribPointerARB");
	
	
	// GLSL Related
	glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)
		SDL_GL_GetProcAddress("glCreateShaderObjectARB");
	glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)
		SDL_GL_GetProcAddress("glShaderSourceARB");
	glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)
		SDL_GL_GetProcAddress("glCompileShaderARB");
	glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)
		SDL_GL_GetProcAddress("glGetObjectParameterivARB");
	glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)
		SDL_GL_GetProcAddress("glGetInfoLogARB");
	glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)
		SDL_GL_GetProcAddress("glCreateProgramObjectARB");
	glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)
		SDL_GL_GetProcAddress("glAttachObjectARB");
	glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)
		SDL_GL_GetProcAddress("glLinkProgramARB");
	glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)
		SDL_GL_GetProcAddress("glUseProgramObjectARB");
	glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)
		SDL_GL_GetProcAddress("glGetUniformLocationARB");
	glGetAttribLocationARB = (PFNGLGETATTRIBLOCATIONARBPROC)
		SDL_GL_GetProcAddress("glGetAttribLocationARB");
	glUniform2fARB			= (PFNGLUNIFORM2FARBPROC)
		SDL_GL_GetProcAddress("glUniform2fARB");
	glUniform3fARB			= (PFNGLUNIFORM3FARBPROC)
		SDL_GL_GetProcAddress("glUniform3fARB");
	glUniform1iARB			= (PFNGLUNIFORM1IARBPROC)
		SDL_GL_GetProcAddress("glUniform1iARB");
	glEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)
		SDL_GL_GetProcAddress("glEnableVertexAttribArrayARB");
	glEnableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) 
		SDL_GL_GetProcAddress("glDisableVertexAttribArrayARB");

#ifndef __GNUC__
	glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)
			SDL_GL_GetProcAddress("glActiveTextureARB");
	glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)
		SDL_GL_GetProcAddress("glClientActiveTexture");
#endif
	
	InitScreen();

	// On screen config changes we need to regen buffers
	WtfGraphics::tGraphicsMode::tCacheRef("v_graphicsMode")->BindNotify(
		new ObjFunctor<Renderer, void>(this, &Renderer::InitScreen));
};

Renderer::~Renderer()
{
};

void Renderer::InitScreen()
{
	Log( LOG_INFO, String("Initializing renderer") );

	iVector2D vRes = WtfGraphics::tGraphicsMode::tCacheRef("v_graphicsMode")->Value.Resolution;

	//
	// Init offscreen rendering, for deferred shading
	//

	mrDeferredShaderData = new FBOTexture();
	mrDeferredShaderData->Create(vRes, 2); // 2 extra render targets

	mrRenderTarget = new FBOTexture();
	mrRenderTarget->Create(vRes);

	ClearPipeline();

	//
	// Load shaders
	//
	
	PostPass::tRef rPass;
	FBOTexture::tRef rTarget;

	AppendPass( new PostPass("d_ssao", new DeferredShader("src/shaders/ssao", mrDeferredShaderData)) );


	// HDR Bloom

	PostPass::tRef rBloomPass = new PostPass("hdr_bloom_downsampler", new DeferredShader("src/shaders/hdr_bloom_downsampler", mrDeferredShaderData));
	rTarget = new FBOTexture();
	rTarget->Create(vRes / 2);
	rBloomPass->rTarget = rTarget;
	AppendPass( rBloomPass );

	rPass = new PostPass("hdr_bloom", new PostProcessSampler("src/shaders/hdr_bloom", rTarget));
	rPass->rSource = rBloomPass->rSource;
	AppendPass( rPass );
};

/**
 * Appends a postprocessing effect to the renderer pipeline.
 * @pre	The main render pass is already in the pipeline
 **/
void Renderer::AppendPass(Renderer::PostPass::tRefa pass)
{
	// Find the pass which renders to the screen (if any)
	GenericPass::tRef rLastPass;
	for( std::vector<GenericPass::tRef>::reverse_iterator lastPass = mPipeline.rbegin();
			lastPass != mPipeline.rend(); lastPass++ ) {
		if( (*lastPass)->rTarget == mScreen ) {
			rLastPass = *lastPass;
			break;
		};
	};

	if(pass->rSource.isNull()) {
		if(rLastPass.isNull()) throw std::logic_error("Renderer: Must specify diffuse data source for this pass");

		// No custom source - we want diffuse data from last pass
		// Use previous shader's result - stored in intermediate buffer
		rLastPass->rTarget = mrNextFreeBuffer;
		pass->rSource = mrNextFreeBuffer;
		mrNextFreeBuffer = mrNextFreeBuffer == mrRenderTarget ? mrDeferredShaderData : mrRenderTarget;
	};

	if(pass->rTarget.isNull())
		pass->rTarget = mScreen;

	mPipeline.push_back(pass);
};

void Renderer::ClearPipeline()
{
	mPipeline.clear();
	mPipeline.push_back( new MainRenderPass() );
	mPipeline[0]->rTarget = mScreen;
	mrNextFreeBuffer = mrRenderTarget;
};

void Renderer::RenderAll() {
	for(tRenderList::iterator iter = this->mRenderList.begin();
		iter != this->mRenderList.end();
		++iter) {
		(*iter)->Render();
	};
};

void Renderer::Render(const WtfEngine::IScreen::tRef &rRenderTarget, const WtfEngine::IPerspective::tRef &rCamera, tRenderType type)
{
	//
	// Setup the rendering target, then render all objects in the render list
	//

	glBindTexture(GL_TEXTURE_RECTANGLE_NV, 0);
	rRenderTarget->Activate();
	glDrawBuffer(GL_COLOR_BUFFER_BIT);

	// Background Colour
	glClearColor (0.5f, 0.6f, 0.8f, 0.0f);
	glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

	rCamera->EnterPerspective(rRenderTarget);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glDepthMask(GL_TRUE);
	glDisable(GL_STENCIL_TEST);
	glDisable(GL_BLEND);

	switch(type) {
		case RENDER_WORLD_WIREFRAME:
			// TODO: enable wireframe
		case RENDER_WORLD:
			// TODO: culling
			RenderAll();
			break;

		case RENDER_PHYSICS:
			PhysicsEngine::GetInstance().getWorld()->debugDrawWorld();
			break;
	};

	rCamera->LeavePerspective();
	rRenderTarget->Deactivate();
};



/**
 * Renders a scene. Clears the screen, set ups the viewport, activates
 * various settings. Then renders the Render List first (3Ds) , followed by 
 * the Overlay Stack (HUD, Developer's Console etc...)
 */
void Renderer::Run()
{
	tOverlayStack::iterator	overlay;
	
	if(mRenderType != RENDER_OVERLAYS_ONLY) {
		for( std::vector<GenericPass::tRef>::const_iterator pass = mPipeline.begin(); pass != mPipeline.end(); pass++ ) {
			(*pass)->Render();
		};
	};

	//
	// Draw overlays
	//

	mScreen->Activate();
	WtfGraphics::SaveProjection();
	WtfGraphics::SetupOrthoProjection(mScreen);

	glPushAttrib(GL_BLEND);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	for(overlay = mOverlayStack.begin();
		overlay != mOverlayStack.end(); overlay++)
	{
		(*overlay)->Render();
		glTranslatef(0, 0, 0.01);
	};

	glPopAttrib();
	WtfGraphics::RestoreProjection();

	mScreen->Deactivate();
	mScreen->UpdateScreen();
};

